/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ricm4.ar.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Xu
 */
public class NioServer implements Runnable {
	private InetAddress hostAddress;
	private int port;
	private ServerSocketChannel serverChannel;
	private Selector selector;
	private ByteBuffer outBuffer;
	private Map<SocketChannel, byte[]> clientMessage = new ConcurrentHashMap<>();
	private int length;
	private ByteBuffer inBuffer;
	
	public NioServer(InetAddress hostAddress, int port) throws IOException{
		this.hostAddress=hostAddress;
		this.port=port;
		selector=SelectorProvider.provider().openSelector();
		
		serverChannel=ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
		
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
	}
		
	@Override
	public void run() {
		while(true){
			try {
				this.selector.select();
				Iterator selectedKeys=this.selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()){
					SelectionKey key=(SelectionKey) selectedKeys.next();
					selectedKeys.remove();
					if(!key.isValid()){
						continue;
					}
					
					if(key.isAcceptable()){
						handleAccept(key);
					}else if(key.isReadable()){
						handleDataIn(key);
					}else if(key.isWritable()){
						handleDataOut(key);
					}
				}
			} catch (IOException ex) {
				Logger.getLogger(NioServer.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}
	
	public static void main(String[] args){
		try {
			new Thread(new NioServer(null, 8888)).start();
		} catch (IOException ex) {
			Logger.getLogger(NioServer.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	private void handleAccept(SelectionKey key) throws IOException {
		ServerSocketChannel serverSocketChannel=(ServerSocketChannel) key.channel();
		
		SocketChannel socketChannel=serverSocketChannel.accept();
		socketChannel.configureBlocking(false);
		
		socketChannel.register(this.selector, SelectionKey.OP_READ);
	}

	private void handleDataIn(SelectionKey key) throws IOException {
		SocketChannel socketChannel=(SocketChannel) key.channel();
		inBuffer=ByteBuffer.allocate(length);
		
		int numRead = -1;
		try {
			numRead=socketChannel.read(inBuffer);
		} catch (IOException ex) {
			key.cancel();
			socketChannel.close();
			return;
		}
		
		if(numRead==-1){
			key.channel().close();
			key.cancel();
			return;
		}
		
		processData(this, socketChannel, this.inBuffer.array(), numRead);
	}

	private void handleDataOut(SelectionKey key) throws IOException {
		SocketChannel socketChannel=(SocketChannel) key.channel();
		try {
			sendData(socketChannel, inBuffer.array());
			socketChannel.write(outBuffer);
		} catch (IOException ex) {
			key.cancel();
			socketChannel.close();
			return;
		}
	}
	
	private void sendData(SocketChannel socketChannel, byte[] data) throws IOException {
		outBuffer.wrap(data);
		SelectionKey key=socketChannel.keyFor(selector);
		key.interestOps(SelectionKey.OP_WRITE);
	}

	private void processData(NioServer aThis, SocketChannel socketChannel, byte[] array, int numRead) {
		byte[] bytes = clientMessage.get(socketChannel); 
		if (bytes == null) { 
			bytes = new byte[0]; 
		} 
		if (numRead > 0) { 
			byte[] newBytes = new byte[bytes.length + numRead]; 
			System.arraycopy(bytes, 0, newBytes, 0, bytes.length); 
			System.arraycopy(array, 0, newBytes, bytes.length, numRead); 
			clientMessage.put(socketChannel, newBytes); 
		}
	}
}
